#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <sys/times.h>
#include <sys/param.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/time.h>
#include "config.h"
#include "userdef.h"

/*------------------Functions----------------------------*/
extern void PrintData(double *Array);
extern void * MyMalloc(size_t size);
extern void * MyCalloc(size_t size);
extern void *Multiply(void *WorkerData);
extern double get_time();

/*-------------------------------------------------------*/


/*-------------------Global Variables--------------------*/
int ThreadCount; /* No of Threads */
pthread_barrier_t Barrier;	/* To synchronize threads */
pthread_mutex_t Mat_Mutex;	/* Mutex for grid updation*/
double TotalTime; /* time counter */

/*-------------------------------------------------------*/
int main(int argc, char* argv[]) {
    pthread_attr_t *Pthread_attr;
    THREAD_DATA *TData; /* Array to hold information to be passed to a thread */
    pthread_t *threads; /* Threads are to be created */
    int A_Rows, A_Cols, B_Rows, B_Cols, C_Rows, C_Cols;
    double *A, *B, *C;
    int blocksize;

    if (argc != 7)
    {
        fprintf(stderr, "Usage: %s <#Threads> <A_Rows> <A_Columns> <B_Rows> <B_Columns> <blocksize>\n", argv[0]);
        exit(0);
    }

    /* Read command line arguments into variables */
    ThreadCount    = atoi(argv[1]);
    A_Rows = atoi(argv[2]);
    B_Rows = atoi(argv[4]);
    A_Cols = atoi(argv[3]);
    B_Cols = atoi(argv[5]);
    blocksize = atoi(argv[6]);

    C_Rows = atoi(argv[2]); /* (M1,N1) X (M2, N2) = (M1, N2) */
    C_Cols = atoi(argv[5]); /* (M1,N1) X (M2, N2) = (M1, N2) */

    /* Allocate memory for threads */
    TData = (THREAD_DATA *) MyMalloc(ThreadCount * sizeof(THREAD_DATA));
    Pthread_attr = (pthread_attr_t *) MyMalloc(sizeof(pthread_attr_t) * ThreadCount );
    threads = (pthread_t *) MyMalloc(ThreadCount * sizeof(pthread_t));

    /* Allocate Memory for Matrices */
    A = (DATA_TYPE *) MyMalloc(A_Rows * A_Cols * sizeof(DATA_TYPE));
    B = (DATA_TYPE *) MyMalloc(B_Rows * B_Cols * sizeof(DATA_TYPE));
    C = (DATA_TYPE *) MyMalloc(C_Rows * C_Cols * sizeof(DATA_TYPE));
    fill(A,A_Rows * A_Cols);
    fill(B,B_Rows * B_Cols);
    fill(C,C_Rows * C_Cols);

    TotalTime = get_time();

    int cntr;
    for (cntr = 0; cntr < ThreadCount; cntr++)
    {
        /* Set thread data */
        TData[cntr].ThreadId  = cntr;
        TData[cntr].numproc   = ThreadCount;
        TData[cntr].A         = A;
        TData[cntr].B         = B;
        TData[cntr].C         = C;
        TData[cntr].A_Rows    = A_Rows;
        TData[cntr].A_Cols    = A_Cols;
        TData[cntr].B_Rows    = B_Rows;
        TData[cntr].B_Cols    = B_Cols;
        TData[cntr].cs         = 1024 * 1024;
        TData[cntr].cd         = 16 * 1024;
        TData[cntr].blocksize  = 96;

        /* Initialize thread attribute */
        pthread_attr_init (&Pthread_attr[cntr]);

        /* Set threads to Joinable */
        pthread_attr_setdetachstate (&Pthread_attr[cntr], PTHREAD_CREATE_JOINABLE);

        int rc = 0;
        /* Create threads calling Multiply */
        rc = pthread_create(&threads[cntr], Pthread_attr, &Multiply, (void *) &TData[cntr]);
        if (rc)
        {
            fprintf(stdout,"ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }

    /* Join threads */
    for (cntr = 0; cntr < ThreadCount; cntr++)
    {
        int rc = pthread_join(threads[cntr], NULL);
        if (rc)
        {
            fprintf(stdout,"ERROR; return code from pthread_join() is %d\n", rc);
            exit(-1);
        }
    }
    /* Time it */
    TotalTime = (get_time() - TotalTime);
    printf("Time taken = %6.2f\n", TotalTime);
    free(threads);
    pthread_attr_destroy(Pthread_attr);
    pthread_mutex_destroy(&Mat_Mutex);

#ifdef MAINDEBUG
    /* Print Permuted Array */
    PrintData(D,4);
#endif

    return 0;

}



